/*
 *  linux/kernel/printk.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * When in kernel-mode, we cannot use printf, as fs is liable to
 * point to 'interesting' things. Make a printf with fs-saving, and
 * all is well.
 */
/*
 * 当处于内核模式时,我们不能使用printf,因为寄存器fs指向其他不感兴趣的地方.自己编制一个printf并在使用前保存fs,一切就解决了.
 */
// 标准参数头文件.以宏的形式定义变量参数列表.主要说明了一个类型(va_list)和三个宏va_start、va_arg和va_end,
// 用于vsprintf,vprintf,vfprintf函数。
#include <stdarg.h>
#include <stddef.h>             			// 标准定义头文件。定义了NULL，offsetof(TYPE,MEMBER)。

#include <linux/kernel.h>       			// 内核头文件。含有一些内核常用函数的原形定义。

static char buf[1024];          			// 显示用临时缓冲区。

// 函数vsprintf()定义在linux/kernel/vsprintf.c中
extern int vsprintf(char * buf, const char * fmt, va_list args);

// 内核使用的显示函数.
int printk(const char *fmt, ...)
{
	va_list args;							// va_list实际上是一个字符指针类型.
	int i;

	// 运行参数处理开始函数.然后使用格式串fmt将参数列表args输出到buf中.返回值i等于输出字符串的长度.再运行参数处理结束函数.最后调用控制台显示
	// 函数并返回显示字符数.
	va_start(args, fmt);
	i = vsprintf(buf, fmt, args);
	va_end(args);
	console_print(buf);						// chr_drv/console.c
	return i;
}

/*
* 保护模式下调试显示函数,内核中虽然有 printk()显示函数,但是它需要调用 tty_write(),
* 在内核没有完全运转起来该函数是不能使用的。这个 check_data32()函数可以在进入保护模式后,
* 在屏幕上打印你感兴趣的东西。起用页功能与否，不影响效果
* 作用：在屏幕上用 16 进制显示一个 32 位整数。
* 参数：value -- 要显示的整数。
* pos -- 屏幕位置,以 16 个字符宽度为单位,例如为 2,即表示从左上角 32 字符宽度处开始显示。
* 返回：无。
* 如果要在汇编程序中用，要保证该函数被编译链接进了内核.gcc 汇编中的用法如下:
* pushl pos //pos 要用你实际的数据代替,例如 pushl $4
* pushl value //pos 和 value 可以是任何合法的寻址方式
* call check_data32
* 参考：《Lim内核完全注释》31页
*/ 
inline void check_data32(int value, int pos)
{
	asm __volatile__(						// %0 – 含有欲显示的值 value；ebx – 屏幕位置
		"shl	$4, %%ebx\n\t"				// 将 pos 值乘 16，在加上 VGA 显示内存起始地址，
		"addl	$0xb8000,%%ebx\n\t"			// ebx 中得到在屏幕左上角开始的显示字符位置。
		"movl	$0xf0000000,%%eax\n\t"		// 设置 4 比特屏蔽码。 
		"movb	$28,%%cl\n\t"				// 设置初始右移比特数值。 
		"1:\n\t"							
		"movl	%0,%%edx\n\t"				// 取欲显示的值 value -> edx
		"andl	%%eax,%%edx\n\t"			// 取 edx 中有 eax 指定的 4 个比特。 
		"shr	%%cl,%%edx\n\t"				// 右移 28 位，edx 中即为所取 4 比特的值。
		"add	$0x30,%%dx\n\t"				// 将该值转换成 ASCII 码。
		"cmp	$0x3a,%%dx\n\t"				// 若该 4 比特数值小于 10，则向前跳转到标号 2 处。
		"jb	2f\n\t"
		"add	$0x07,%%dx\n\t"				// 否则再加上 7，将值转换成对应字符 A—F。
		"2:\n\t"
		"add	$0x0c00,%%dx\n\t"			// 设置显示属性。
		"movw	%%dx,(%%ebx)\n\t"			// 将该值放到显示内存中。 
		"sub	$0x04,%%cl\n\t"				// 准备显示下一个 16 进制数，右移比特位数减 4。
		"shr	$0x04,%%eax\n\t"			// 比特位屏蔽码右移 4 位。
		"add	$0x02,%%ebx\n\t"			// 更新显示内存位置。
		"cmpl	$0x0,%%eax\n\t"				// 屏蔽码值已经移出右端（已经显示完 8 个 16 进制数）？ 
		"jnz	1b\n"						// 还有数值需要显示，则向后跳转到标号 1 处。 
		::"m"(value), "b"(pos));
}

